pub mod spaghetti_source {

pub mod graph {

    pub mod articulation_point {
        use super::{Edge, Graph};
        use std::cmp::{max, min, Ordering};
        use std::collections::{BTreeSet, VecDeque};
        pub fn articulation_point(g: &Graph) -> (Vec<usize>, Vec<EdgeSet>) {
            let n = g.len();
            let mut low = vec![0 ; n];
            let mut num = vec![0 ; n];
            let mut art = BTreeSet::new();
            let mut bcomp = Vec::new();
            let mut s = VecDeque::new();
            for u in 0..n {
                if num[u] == 0 {
                    let mut time = 0;
                    visit(g, u, &mut art, &mut bcomp, &mut s, &mut num,
                          &mut low, &mut time);
                }
            }
            (art.into_iter().collect(), bcomp)
        }
        fn visit(g: &Graph, v: usize, art: &mut BTreeSet<usize>,
                 bcomp: &mut Vec<EdgeSet>, s: &mut VecDeque<Edge>,
                 num: &mut Vec<i32>, low: &mut Vec<i32>, time: &mut i32) {
            *time += 1;
            low[v] = *time;
            num[v] = *time;
            for e in &g[v] {
                let w = e.dst;
                if num[w] < num[v] { s.push_back(e.clone()); }
                if num[w] == 0 {
                    visit(g, w, art, bcomp, s, num, low, time);
                    low[v] = min(low[v], low[w]);
                    if (num[v] == 1 && num[w] != 2) ||
                           (num[v] != 1 && low[w] >= num[v]) {
                        art.insert(v);
                    }
                    if low[w] >= num[v] {
                        bcomp.push(EdgeSet::new());
                        while let Some(f) = s.pop_back() {
                            let n = bcomp.len();
                            let end = f.src == v && f.dst == w;
                            bcomp[n - 1].insert(UndirectEdge(f));
                            if end { break ; }
                        }
                    }
                } else { low[v] = min(low[v], num[w]); }
            }
        }
        pub type EdgeSet = BTreeSet<UndirectEdge>;
        #[derive(Eq, PartialEq, Debug)]
        pub struct UndirectEdge(Edge);
        impl UndirectEdge {
            pub fn min_v(&self) -> usize { min(self.0.src, self.0.dst) }
            pub fn max_v(&self) -> usize { max(self.0.src, self.0.dst) }
        }
        impl Ord for UndirectEdge {
            fn cmp(&self, other: &UndirectEdge) -> Ordering {
                self.min_v().cmp(&other.min_v()).then_with(||
                                                               self.max_v().cmp(&other.max_v()))
            }
        }
        impl PartialOrd for UndirectEdge {
            fn partial_cmp(&self, other: &UndirectEdge) -> Option<Ordering> {
                Some(self.cmp(other))
            }
        }
    }
    pub mod bridge {
        use super::{Edge, Graph};
        use std::collections::VecDeque;
        pub fn bridge(g: &Graph) -> (Vec<Edge>, Vec<Vec<usize>>) {
            let n = g.len();
            let mut num = vec![0 ; n];
            let mut in_stq = vec![false ; n];
            let mut roots = VecDeque::new();
            let mut stq = VecDeque::new();
            let mut time = 0;
            let mut brdg = VecDeque::new();
            let mut tecomp = Vec::new();
            for u in 0..n {
                if num[u] == 0 {
                    visit(g, u, n, &mut brdg, &mut tecomp, &mut roots,
                          &mut stq, &mut in_stq, &mut num, &mut time);
                    brdg.pop_back();
                }
            }
            (brdg.into_iter().collect(), tecomp)
        }
        fn visit(g: &Graph, v: usize, u: usize, brdg: &mut VecDeque<Edge>,
                 tecomp: &mut Vec<Vec<usize>>, roots: &mut VecDeque<usize>,
                 stq: &mut VecDeque<usize>, in_stq: &mut Vec<bool>,
                 num: &mut Vec<usize>, time: &mut usize) {
            *time += 1;
            num[v] = *time;
            stq.push_back(v);
            in_stq[v] = true;
            roots.push_back(v);
            for e in &g[v] {
                let w = e.dst;
                if num[w] == 0 {
                    visit(g, w, v, brdg, tecomp, roots, stq, in_stq, num,
                          time);
                } else if u != w && in_stq[w] {
                    while num[*roots.back().unwrap()] > num[w] {
                        roots.pop_back();
                    }
                }
            }
            if v == *roots.back().unwrap() {
                brdg.push_back(Edge{src: u, dst: v, weight: 0,});
                tecomp.push(Vec::new());
                loop  {
                    let w = stq.pop_back().unwrap();
                    in_stq[w] = false;
                    let n = tecomp.len();
                    tecomp[n - 1].push(w);
                    if v == w { break ; }
                }
                roots.pop_back();
            }
        }
    }
    use std::cmp::Ordering;
    pub type Weight = i32;
    #[derive(Eq, PartialEq, Debug, Clone)]
    pub struct Edge {
        pub src: usize,
        pub dst: usize,
        pub weight: Weight,
    }
    impl Ord for Edge {
        fn cmp(&self, other: &Edge) -> Ordering {
            self.weight.cmp(&other.weight).reverse().then_with(||
                                                                   self.src.cmp(&other.src)).then_with(||
                                                                                                           self.dst.cmp(&other.dst))
        }
    }
    impl PartialOrd for Edge {
        fn partial_cmp(&self, other: &Edge) -> Option<Ordering> {
            Some(self.cmp(other))
        }
    }
    pub type Edges = Vec<Edge>;
    pub type Graph = Vec<Edges>;
    pub type Array = Vec<Weight>;
    pub type Matrix = Vec<Array>;
}
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() { assert_eq!(2 + 2 , 4); }
}
}


use spaghetti_source::graph::bridge::bridge;
use spaghetti_source::graph::{Edge, Edges};

fn main() {
    let (n, m) = readln();
    let mut g = vec![Edges::new(); n];
    for _ in 0..m {
        let (x, y): (usize, usize) = readln();
        g[x].push(Edge {
            src: x,
            dst: y,
            weight: 0,
        });
        g[y].push(Edge {
            src: y,
            dst: x,
            weight: 0,
        });
    }
    let (brdg, _) = bridge(&g);
    let mut answer = brdg.into_iter()
        .map(|e| Edge {
            src: std::cmp::min(e.src, e.dst),
            dst: std::cmp::max(e.src, e.dst),
            weight: e.weight,
        })
        .collect::<Vec<_>>();
    answer.sort();
    for e in answer {
        println!("{} {}", e.src, e.dst);
    }
}

pub trait FromLn {
    fn fromln(s: &str) -> Self;
}
pub fn readln<T: FromLn>() -> T {
    let mut buf = String::new();
    let _ = ::std::io::stdin().read_line(&mut buf).unwrap();
    T::fromln(buf.trim())
}
pub fn readlns<T: FromLn>(n: usize) -> Vec<T> {
    let mut vs = vec![];
    for _ in 0..n {
        vs.push(readln());
    }
    vs
}
macro_rules! fromln_primitives {
    ($($t:ty),*) => { $(
        impl FromLn for $t {
            fn fromln(s: &str) -> $t {
                s.parse().unwrap()
            }
        }
    )* }
}
fromln_primitives!(String, bool, f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64);
impl<T> FromLn for Vec<T>
where
    T: FromLn,
{
    fn fromln(s: &str) -> Vec<T> {
        s.split_whitespace().map(T::fromln).collect()
    }
}
impl FromLn for Vec<char> {
    fn fromln(s: &str) -> Vec<char> {
        s.chars().collect()
    }
}
macro_rules! fromln_tuple {
    ($($t:ident),*) => {
        impl<$($t),*> FromLn for ($($t),*) where $($t: FromLn),* {
            fn fromln(s: &str) -> ($($t),*) {
                let mut it = s.split_whitespace();
                let t = ($($t::fromln(it.next().unwrap())),*);
                assert_eq!(it.next(), None);
                t
            }
        }
    }
}
fromln_tuple!(A, B);
fromln_tuple!(A, B, C);
fromln_tuple!(A, B, C, D);
fromln_tuple!(A, B, C, D, E);
fromln_tuple!(A, B, C, D, E, F);



Measured memory_bytes: 10084